﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using Common.Utilities;
using Workflows.Components.Entities.Distances;

namespace Workflows.Components.Entities
{
    [Serializable]
    public class AttributeMetaData
    {
        private string _AttributeName;
        /// <summary></summary>
        public string AttributeName
        {
            get
            {
                return this._AttributeName;
            }
        }

        private Dictionary<string,string> _ArgNameAttrNameBindings;
        /// <summary>only used when attribute type is CompositeAttribute</summary>
        public Dictionary<string,string> ArgNameAttrNameBindings
        {
            get
            {
                return this._ArgNameAttrNameBindings;
            }
            set
            {
                this._ArgNameAttrNameBindings = value;
            }
        }

        private string _ComparerAssemblyName;
        /// <summary></summary>
        public string ComparerAssemblyName
        {
            get
            {
                return this._ComparerAssemblyName;
            }
            set
            {
                this._ComparerAssemblyName = value;
            }
        }

        private string _ComparerTypeName;
        /// <summary></summary>
        public string ComparerTypeName
        {
            get
            {
                return this._ComparerTypeName;
            }
            set
            {
                this._ComparerTypeName = value;
            }
        }

        private Dictionary<string,string> _ComparerSettings;
        /// <summary></summary>
        public Dictionary<string,string> ComparerSettings
        {
            get
            {
                return this._ComparerSettings;
            }
            set
            {
                this._ComparerSettings = value;
            }
        }

        private AttributeType _AttributeType;
        /// <summary></summary>
        public AttributeType AttributeType
        {
            get
            {
                return this._AttributeType;
            }
            set
            {
                this._AttributeType = value;
            }
        }

        private double _PositiveMatchWeight;
        /// <summary></summary>
        public double PositiveMatchWeight
        {
            get
            {
                return this._PositiveMatchWeight;
            }
            set
            {
                this._PositiveMatchWeight = value;
            }
        }

        private double _NegativeMatchWeight;
        /// <summary></summary>
        public double NegativeMatchWeight
        {
            get
            {
                return this._NegativeMatchWeight;
            }
            set
            {
                this._NegativeMatchWeight = value;
            }
        }

        private bool _IsExternalAttribute;
        /// <summary></summary>
        public bool IsExternalAttribute
        {
            get
            {
                return this._IsExternalAttribute;
            }
            set
            {
                this._IsExternalAttribute = value;
            }
        }

        private DataProviderType _ExternalAttributeDbProvider;
        /// <summary></summary>
        public DataProviderType ExternalAttributeDbProvider
        {
            get
            {
                return this._ExternalAttributeDbProvider;
            }
            set
            {
                this._ExternalAttributeDbProvider = value;
            }
        }

        private string _ExternalAttributeConnStr;
        /// <summary></summary>
        public string ExternalAttributeConnStr
        {
            get
            {
                return this._ExternalAttributeConnStr;
            }
            set
            {
                this._ExternalAttributeConnStr = value;
            }
        }

        private string _ExternalAttributeSelectSql;
        /// <summary></summary>
        public string ExternalAttributeSelectSql
        {
            get
            {
                return this._ExternalAttributeSelectSql;
            }
            set
            {
                this._ExternalAttributeSelectSql = value;
            }
        }

        public AttributeMetaData(string name, AttributeType attrType, 
            string comparerAssemName, string comparerTypeName,
            Dictionary<string,string> comparerSettings, 
            double positiveWeight, double negativeWeight,
            bool isExternalAttr, DataProviderType dbProvider,
            string connStr, string selectSql)
        {
            this._AttributeName = name;
            this._ArgNameAttrNameBindings = new Dictionary<string, string>();
            this._AttributeType = attrType;
            this._ComparerAssemblyName = comparerAssemName;
            this._ComparerTypeName = comparerTypeName;
            this._ComparerSettings = comparerSettings;
            this._PositiveMatchWeight = positiveWeight;
            this._NegativeMatchWeight = negativeWeight;

            this._IsExternalAttribute = isExternalAttr;
            this._ExternalAttributeDbProvider = dbProvider;
            this._ExternalAttributeConnStr = connStr;
            this._ExternalAttributeSelectSql = selectSql;
        }

        public AttributeMetaData(Dictionary<string,string> compositeAttrBindings, 
            string comparerAssemName, string comparerTypeName, 
            Dictionary<string,string> comparerSettings, 
            double positiveWeight, double negativeWeight,
            bool isExternalAttr, DataProviderType dbProvider,
            string connStr, string selectSql)
        {
            this._ArgNameAttrNameBindings = compositeAttrBindings;
            string[] attrNames=new string[this._ArgNameAttrNameBindings.Count];
            this._ArgNameAttrNameBindings.Values.CopyTo(attrNames,0);
            this._AttributeName = StringUtil.ToString(attrNames);
            
            this._AttributeType = Entities.AttributeType.CompositeAttribute;
            this._ComparerAssemblyName = comparerAssemName;
            this._ComparerTypeName = comparerTypeName;
            this._ComparerSettings = comparerSettings;
            this._PositiveMatchWeight = positiveWeight;
            this._NegativeMatchWeight = negativeWeight;

            this._IsExternalAttribute = isExternalAttr;
            this._ExternalAttributeDbProvider = dbProvider;
            this._ExternalAttributeConnStr = connStr;
            this._ExternalAttributeSelectSql = selectSql;
        }

        /// <summary>
        /// if attr is number, return avg, else return mode
        /// </summary>
        /// <param name="attrValues"></param>
        /// <returns></returns>
        public object GetAttributeAverageValues(object[] attrValues)
        {
            if (attrValues != null && attrValues.Length > 0)
            {
                object firstObj = attrValues[0];
                if (FieldDataType.IsNumber(SqlDbTypeUtil.ToSqlDbType(firstObj.GetType())))
                {
                    List<double> doubleValues = new List<double>();
                    foreach (object obj in attrValues)
                    {
                        if (obj != null)
                        {
                            double dblValue;
                            double.TryParse(obj.ToString(), out dblValue);
                            if (dblValue != null)
                                doubleValues.Add(dblValue);
                        }
                    }
                    return StatsUtil.GetAverage(doubleValues);
                }
                else
                {
                    object mode = firstObj;
                    int count = 1;
                    Dictionary<object, int> modeCount = new Dictionary<object, int>();
                    foreach (object obj in attrValues)
                    {
                        if (modeCount.ContainsKey(obj))
                        {
                            modeCount[obj] = modeCount[obj] + 1;
                            if (modeCount[obj] > count)
                            {
                                count = modeCount[obj];
                                mode = obj;
                            }
                        }
                        else
                        {
                            modeCount.Add(obj, 1);
                        }
                    }
                    return mode;
                }
            }
            else
                return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="attrValues"></param>
        /// <param name="weights"></param>
        /// <returns></returns>
        public object GetAttributeWeightedValue(object[] attrValues, double[] weights)
        {
            if (attrValues != null && attrValues.Length > 0)
            {
                object firstObj = attrValues[0];
                if (FieldDataType.IsNumber(SqlDbTypeUtil.ToSqlDbType(firstObj.GetType())))
                {
                    double sum = 0;
                    double weightSum = 0;
                    for (int i = 0; i < attrValues.Length; i++)
                    {
                        object obj = attrValues[i];
                        double weight = 1.0;
                        if (weights != null && weights.Length == attrValues.Length)
                        {
                            weight = weights[i];
                        }
                        if (obj != null)
                        {
                            double dblValue;
                            double.TryParse(obj.ToString(), out dblValue);
                            if (dblValue != null)
                            {
                                sum += dblValue * weight;
                                weightSum += weight;
                            }
                        }
                    }

                    if (weightSum > 0)
                        return sum / weightSum;
                    else
                        return double.NaN;
                }
                else
                {
                    object mode = firstObj;
                    int count = 1;
                    Dictionary<object, int> modeCount = new Dictionary<object, int>();
                    foreach (object obj in attrValues)
                    {
                        if (modeCount.ContainsKey(obj))
                        {
                            modeCount[obj] = modeCount[obj] + 1;
                            if (modeCount[obj] > count)
                            {
                                count = modeCount[obj];
                                mode = obj;
                            }
                        }
                        else
                        {
                            modeCount.Add(obj, 1);
                        }
                    }
                    return mode;
                }
            }
            else
                return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="attrValues"></param>
        /// <returns></returns>
        public object GetAttributeModeValue(object[] attrValues)
        {
            //if (this.AttributeType == AttributeType.VectorAttribute)
            //    return this.GetUnionOfVectorAttributeValues(attrValues);

            if (attrValues != null && attrValues.Length > 0)
            {
                object firstObj = attrValues[0];
                object mode = firstObj;
                int count = 1;
                Dictionary<object, int> modeCount = new Dictionary<object, int>();
                foreach (object obj in attrValues)
                {
                    if(obj==null)
                        continue;
                    if (modeCount.ContainsKey(obj))
                    {
                        modeCount[obj] = modeCount[obj] + 1;
                        if (modeCount[obj] > count)
                        {
                            count = modeCount[obj];
                            mode = obj;
                        }
                    }
                    else
                    {
                        modeCount.Add(obj, 1);
                    }
                }
                return mode;
            }
            else
                return null;
        }

        //private string GetUnionOfVectorAttributeValues(object[] attrValues)
        //{
        //    List<string> unionVec = new List<string>();
        //    if (attrValues != null && attrValues.Length > 0)
        //    {
        //        foreach(object attrVal in attrValues)
        //        {
        //            if(attrVal !=null && attrVal.ToString().Length>0)
        //            {
        //                string[] features = StringUtil.Split(attrVal.ToString());
        //                if(features !=null && features.Length>0)
        //                {
        //                    foreach(string feature in features)
        //                    {
        //                        if(!unionVec.Contains(feature.ToUpper()))
        //                        {
        //                            unionVec.Add(feature.ToUpper());
        //                        }
        //                    }
        //                }
        //            }
        //        }
        //    }

        //    return StringUtil.ToString(unionVec.ToArray());
        //}

        public IAttributeDistanceCalcutor GetComparer()
        {
            string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string assemFilePath = Path.Combine(binFolderPath, this.ComparerAssemblyName + ".dll");
            if (!File.Exists(assemFilePath))
                return null;

            Assembly assembly = Assembly.LoadFile(assemFilePath);
            Type type = assembly.GetType(this.ComparerTypeName);
            IAttributeDistanceCalcutor comparer = Activator.CreateInstance(type) as IAttributeDistanceCalcutor;
            if (comparer == null)
                return null;
            if (this.ComparerSettings != null && this.ComparerSettings.Count > 0)
            {
                comparer.ComparerSettings = this.ComparerSettings;
            }
            comparer.InitInMemorySettings();
            return comparer;
        }
    }
}
